#include "hash_table.h"
#include "env.h"
#include <stdio.h>

vm_hash_table* hash_init(int capacity) {
    vm_hash_table* hash_table = (vm_hash_table *) malloc(sizeof(vm_hash_table) + 1);
    hash_table->data = (vm_hash_table_block **) calloc(capacity, sizeof(vm_hash_table_block*));
    for (int i = 0; i < capacity; i++) {
        hash_table->data[i] = NULL;
    }
    hash_table->count = 0;
    hash_table->capacity = capacity;
    return hash_table;
}

int hash_key(vm_hash_table* hash_table, const char* key) {
    int sum = 0;
    int cursor = 0;
    while (key[cursor] != '\0') {
        sum += (int)key[cursor];
        cursor++;
    }
    return sum % hash_table->capacity;
}

int hash_add(vm_hash_table* hash_table, char* key, void* element, int size_of_element) {
    int hash_code = hash_key(hash_table, key);
    if (hash_table->capacity - 1 < hash_code) {
        return 0;
    }
    if (hash_table->data[hash_code] == NULL) {
        hash_table->data[hash_code] = (vm_hash_table_block *) malloc(sizeof(vm_hash_table_block) + 1);
        hash_table->data[hash_code]->next = NULL;
    }
    vm_hash_table_block* current = hash_table->data[hash_code];
    while (current->next != NULL) {
        if (strcmp(current->key, key) == 0) {
            return 0;
        }
        current = current->next;
    }
    vm_hash_table_block* new_node = (vm_hash_table_block *) malloc(sizeof(vm_hash_table_block) + 1);
    new_node->hash_code = hash_code;
    strcpy(new_node->key, key);
    new_node->value = (void *) malloc(size_of_element + 1);
    memcpy(new_node->value, element, size_of_element);
    new_node->next = NULL;
    current->next = new_node;
    hash_table->count++;
    return 1;
}

int hash_contains(vm_hash_table* hash_table, char* key) {
    int hash_code = hash_key(hash_table, key);
    if (hash_table->data[hash_code] == NULL) {
        return 0;
    }
    vm_hash_table_block* current = hash_table->data[hash_code];
    while ((current = current->next) != NULL) {
        if (strcmp(current->key, key) == 0) {
            return 1;
        }
    }
    return 0;
}

vm_hash_table_block* hash_find(vm_hash_table* hash_table, char* key) {
    int hash_code = hash_key(hash_table, key);
    if (hash_table->data[hash_code] == NULL) {
        return NULL;
    }
    vm_hash_table_block* current = hash_table->data[hash_code];
    while ((current = current->next) != NULL) {
        if (strcmp(current->key, key) == 0) {
            return current;
        }
    }
    return NULL;
}

void hash_remove(vm_hash_table* hash_table, char* key) {
    int hash_code = hash_key(hash_table, key);
    if (hash_table->data[hash_code] == NULL) {
        return;
    }
    vm_hash_table_block* current = hash_table->data[hash_code];
    vm_hash_table_block* previous = current;
    while ((current = current->next) != NULL) {
        if (strcmp(current->key, key) == 0) {
            previous->next = current->next;
            VM_FREE(current->value);
            VM_FREE(current);
            break;
        }
        previous = current;
    }
}

void hash_destroy(vm_hash_table* hash_table) {
    for (int i = 0; i < hash_table->capacity; i++) {
        if (hash_table->data[i] != NULL) {
            vm_hash_table_block* current = hash_table->data[i];
            vm_hash_table_block* previous = current;
            while ((current = current->next) != NULL) {
                VM_FREE(previous->value);
                VM_FREE(previous);
                previous = current;
            }
        }
    }
    VM_FREE(hash_table);
}
